How to center horizontally UICollectionView Cells?
Asked Answered
N

33

172

I have done some research, but I couldn't find any code example on how to center cells in a UICollectionView horizontally.

instead of the first cell being like this X00, I want it to be like this 0X0. is there any way to accomplish this?

EDIT:

to visualize what I want:

enter image description here

I need it to look like version B when there is only one element in the CollectionView. When I got more than one element, then it should be like version A but with more elements.

At the moment it looks like Version A when I have only 1 element, and I wonder how I can make it look like B.

Thanks for the help!

Neurocoele answered 14/12, 2015 at 12:57 Comment(7)
Isn't it easier to let the cell fit the width of the collection view and then center the collection view itself within its parent?Stephine
yes, there at least two way to do this, first (fast) is to make cell width of whole screen and center its child view. second (right) implement custom collection view layoutDuteous
There will be eventually more cells coming from the backend, filling the whole width would not be a good ideaNeurocoele
increase the width is enough to set at centreLanceted
Possible duplicate of How to center align the cells of a UICollectionView?Hack
Take a look at this solution: https://mcmap.net/q/144800/-how-to-center-align-the-cells-of-a-uicollectionviewHack
I solved all the issues from the below solutions in my lower comment https://mcmap.net/q/142773/-how-to-center-horizontally-uicollectionview-cellsOkajima
D
300

Its not a good idea to use a library, if your purpose is only this i.e to centre align.

Better you can do this simple calculation in your collectionViewLayout function.

func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAtIndex section: Int) -> UIEdgeInsets {

    let totalCellWidth = CellWidth * CellCount
    let totalSpacingWidth = CellSpacing * (CellCount - 1)

    let leftInset = (collectionViewWidth - CGFloat(totalCellWidth + totalSpacingWidth)) / 2
    let rightInset = leftInset

    return UIEdgeInsets(top: 0, left: leftInset, bottom: 0, right: rightInset)
}
Debenture answered 6/5, 2016 at 6:39 Comment(17)
@Darshan Patel what's exactly CellSpacing ?Boehmenist
CellSpacing is the spacing value you need between two cells... set as zero if you don't need any spacing.Debenture
Doesn't this have to be @override function ... ?Agram
@DarshanPatel.Thanks a lot of the answer I implemented this and the rows came in center as it should but now the problem is I can't scroll to the first item. When I tries to scroll to first item it brings me back to the modified UIEdgeInsets. You can see my demo app github.com/anirudha-music/CollectionViewCenterMcwherter
In Swift 3, the new method signature is collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int).Tertia
consider the case where CellCount is 0.Michey
how are you able to grab the cellWidth, is it possible through the method cellForItemAt? i tried and it returned nil.. cell width for me changes based on the screen size.. @DarshanPatel.Doridoria
@Doridoria Indeed you are right, if cellWidth is not hard coded, this is more complexFleeta
@Doridoria @Balanced cast your UICollectionViewLayout to a UICollectionViewFlowLayout and use the itemSize property to get the cell width.Hummocky
@DarshanPatel. Your answer can sometimes produce a negative value on smaller devices. Consider using a max check on you leftInset value like so: let leftInset = max(0.0, (self.collectionView.bounds.width - CGFloat(totalCellWidth + totalSpacingWidth)) / 2)Hummocky
If you are not subclassing UICollectionViewController, make sure your class conforms to UICollectionViewDelegateFlowLayout otherwise it won't workZink
@RhuariGlen I have an exact problem on iPhone 8, I did what you said but still, I have the problemBroddie
Scrolling was an issue, but yeah this solution works like a charm. Plus to solve scrolling issue try this -> collectionView.bounces = true; collectionView.alwaysBounceHorizontal = true .Maleficent
But this will make cell at center for whole section. I want to set for specific row. How can I acheive that?Scagliola
for anyone who wants to get the itemWidth use: let layout = collectionView.collectionViewLayout as! UICollectionViewFlowLayout; let itemWidth = layout.itemSize.widthShirting
no need for let rightInset = leftInset... just use one let inset. I had to modify the answer to make it work when there are cells that can't fit the screen: https://mcmap.net/q/142773/-how-to-center-horizontally-uicollectionview-cellsTrainload
It's not working for me, first cell working nice, but other cells are showing to much on the left.Dermatology
M
83

Swift 5.1

func centerItemsInCollectionView(cellWidth: Double, numberOfItems: Double, spaceBetweenCell: Double, collectionView: UICollectionView) -> UIEdgeInsets {
    let totalWidth = cellWidth * numberOfItems
    let totalSpacingWidth = spaceBetweenCell * (numberOfItems - 1)
    let leftInset = (collectionView.frame.width - CGFloat(totalWidth + totalSpacingWidth)) / 2
    let rightInset = leftInset
    return UIEdgeInsets(top: 0, left: leftInset, bottom: 0, right: rightInset)
}

Swift 4.2

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {

    let totalCellWidth = 80 * collectionView.numberOfItems(inSection: 0)
    let totalSpacingWidth = 10 * (collectionView.numberOfItems(inSection: 0) - 1)

    let leftInset = (collectionView.layer.frame.size.width - CGFloat(totalCellWidth + totalSpacingWidth)) / 2
    let rightInset = leftInset

    return UIEdgeInsets(top: 0, left: leftInset, bottom: 0, right: rightInset)

}

Swift 3

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAtIndex section: Int) -> UIEdgeInsets {

        let totalCellWidth = 80 * collectionView.numberOfItems(inSection: 0)
        let totalSpacingWidth = 10 * (collectionView.numberOfItems(inSection: 0) - 1)

        let leftInset = (collectionView.layer.frame.size.width - CGFloat(totalCellWidth + totalSpacingWidth)) / 2
        let rightInset = leftInset

        return UIEdgeInsetsMake(0, leftInset, 0, rightInset)

    }

don't forget to add the protocol

UICollectionViewDelegateFlowLayout
Medorra answered 19/4, 2017 at 12:49 Comment(6)
constraint maybe @ashForIosMedorra
Your answer works fine when there are cells that fit in collectionView when cell count increases cells are center and you cannot scroll to first cells or last.Uncinus
@Vitalii 80 mean cell Width, and 10 the space between cells, if you read the variable name you would understand what it mean :PMedorra
The Swift 4.2 solution is easier, THANKS! Just be sure to set the "80" to whatever your actual cell-object width is.Scenic
It's not working for me, first cell working nice, but other cells are showing to much on the left.Dermatology
@ee.bt make sure the edges is set to 0 and make sure that you are using rows not sectionsMedorra
T
30

Try this for Swift 4

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
        let cellWidth : CGFloat = 165.0
        
        let numberOfCells = floor(collectionView.frame.size.width / cellWidth)
        let edgeInsets = (collectionView.frame.size.width - (numberOfCells * cellWidth)) / (numberOfCells + 1)
        
        return UIEdgeInsetsMake(15, edgeInsets, 0, edgeInsets)
    }

Add your cellWidth instead 165.0

Tube answered 1/11, 2017 at 14:15 Comment(2)
this is the best answer. With the simplest math into it. works with any numbers of rows and columnsDrosophila
It's not working for me, first cell working nice, but other cells are showing to much on the left.Dermatology
L
24

I use KTCenterFlowLayout for this, and it works great. It's a custom subclass of UICollectionViewFlowLayout that centres cells as you want. (Note: this isn't a trivial thing to solve by posting some code, which is why I'm linking to a GitHub project!)

Longways answered 14/12, 2015 at 16:11 Comment(1)
Couldn't make it work from IB. This library worked like a charm for me. Just installed the pod and changed the class of layout in IB!Caloric
L
19

An objective-C version of Darshan Patel's answer:

- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(nonnull UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {
    CGFloat totalCellWidth = kItemWidth * self.dataArray.count;
    CGFloat totalSpacingWidth = kSpacing * (((float)self.dataArray.count - 1) < 0 ? 0 :self.dataArray.count - 1);
    CGFloat leftInset = (self.bounds.size.width - (totalCellWidth + totalSpacingWidth)) / 2;
    CGFloat rightInset = leftInset;
    UIEdgeInsets sectionInset = UIEdgeInsetsMake(0, leftInset, 0, rightInset);
    return sectionInset;
}
Leucopoiesis answered 26/10, 2016 at 0:4 Comment(1)
thanks. Its working fine with single row. but creating issue in multiple row. i m not able to add url here to show u screen shot. . but you can add "yynoalzg" in tiny url. u will get idea. Gold section have 4 record. 4th should be in new line. .but after this method it display like that... let me know if any idea.Plicate
O
9

You can use this extension (Swift 4).

It can center cells with if you collectionView have layout.estimatedItemSize = UICollectionViewFlowLayoutAutomaticSize.

It work with any cells size and work perfectly when scrollDirection = .horizontal

public extension UICollectionView {
    func centerContentHorizontalyByInsetIfNeeded(minimumInset: UIEdgeInsets) {
        guard let layout = collectionViewLayout as? UICollectionViewFlowLayout,
            layout.scrollDirection == .horizontal else {
                assertionFailure("\(#function): layout.scrollDirection != .horizontal")
                return
        }

        if layout.collectionViewContentSize.width > frame.size.width {
            contentInset = minimumInset
        } else {
            contentInset = UIEdgeInsets(top: minimumInset.top,
                                        left: (frame.size.width - layout.collectionViewContentSize.width) / 2,
                                        bottom: minimumInset.bottom,
                                        right: 0)
        }
    }
}


final class Foo: UIViewController {
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        collectionView.centerContentHorizontalyByInsetIfNeeded(minimumInset: yourDefaultInset)
    }
}

Hope it's help you!

Organza answered 1/8, 2018 at 15:13 Comment(1)
Getting error: Thread 1: EXC_BAD_ACCESS (code=2, address=0x118ffde58)Embroideress
E
9

General solution for flowlayout that centers the pages if they are less than the width and aligns left if there are more

- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(nonnull UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {
    // Centering if there are fever pages
    CGSize itemSize = [(UICollectionViewFlowLayout *)collectionViewLayout itemSize];
    CGFloat spacing = [(UICollectionViewFlowLayout *)collectionViewLayout minimumLineSpacing];

    NSInteger count = [self collectionView:self numberOfItemsInSection:section];
    CGFloat totalCellWidth = itemSize.width * count;
    CGFloat totalSpacingWidth = spacing * ((count - 1) < 0 ? 0 : count - 1);
    CGFloat leftInset = (self.bounds.size.width - (totalCellWidth + totalSpacingWidth)) / 2;
    if (leftInset < 0) {
        UIEdgeInsets inset = [(UICollectionViewFlowLayout *)collectionViewLayout sectionInset];
        return inset;
    }
    CGFloat rightInset = leftInset;
    UIEdgeInsets sectionInset = UIEdgeInsetsMake(0, leftInset, 0, rightInset);
    return sectionInset;
}

Swift version (converted from ObjC)

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
    // Centering if there are fever pages
    let itemSize: CGSize? = (collectionViewLayout as? UICollectionViewFlowLayout)?.itemSize
    let spacing: CGFloat? = (collectionViewLayout as? UICollectionViewFlowLayout)?.minimumLineSpacing

    let count: Int = self.collectionView(self, numberOfItemsInSection: section)
    let totalCellWidth = (itemSize?.width ?? 0.0) * CGFloat(count)
    let totalSpacingWidth = (spacing ?? 0.0) * CGFloat(((count - 1) < 0 ? 0 : count - 1))
    let leftInset: CGFloat = (bounds.size.width - (totalCellWidth + totalSpacingWidth)) / 2
    if leftInset < 0 {
        let inset: UIEdgeInsets? = (collectionViewLayout as? UICollectionViewFlowLayout)?.sectionInset
        return inset!
    }
    let rightInset: CGFloat = leftInset
    let sectionInset = UIEdgeInsets(top: 0, left: Float(leftInset), bottom: 0, right: Float(rightInset))
    return sectionInset
}

Untitled-3.png

Esquibel answered 2/3, 2019 at 13:23 Comment(3)
What is bounds in swift code? i am getting Use of unresolved identifier 'bounds' errorEuphonium
Hi, in my example i implemented the method inside a UIView, which has bounds, if you are implementing it elsewhere, use the appropriate boundsEsquibel
simple and best answerFireweed
R
7

Slightly modifying @Safad Funy's answer, this is what worked for me in the lattest version of Swift & iOS. In this case I wanted the cells's width to be a third of the size of the collection view.

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {

  let totalCellWidth = Int(collectionView.layer.frame.size.width) / 3 * collectionView.numberOfItems(inSection: 0)
  let totalSpacingWidth = (collectionView.numberOfItems(inSection: 0) - 1)

  let leftInset = (collectionView.layer.frame.size.width - CGFloat(totalCellWidth + totalSpacingWidth)) / 2
  let rightInset = leftInset

  return UIEdgeInsetsMake(0, leftInset, 0, rightInset)
}
Remarque answered 5/5, 2017 at 15:33 Comment(1)
This worked for me especially, when there is only one cell.Stall
Z
7

Here is the newer version for Swift 5 which also works fine when the cells are more than one row:

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
    let flowLayout = collectionViewLayout as! UICollectionViewFlowLayout
    let cellWidth: CGFloat = flowLayout.itemSize.width
    let cellSpacing: CGFloat = flowLayout.minimumInteritemSpacing
    var cellCount = CGFloat(collectionView.numberOfItems(inSection: section))
    var collectionWidth = collectionView.frame.size.width
    var totalWidth: CGFloat
    if #available(iOS 11.0, *) {
        collectionWidth -= collectionView.safeAreaInsets.left + collectionView.safeAreaInsets.right
    }
    repeat {
        totalWidth = cellWidth * cellCount + cellSpacing * (cellCount - 1)
        cellCount -= 1
    } while totalWidth >= collectionWidth

    if (totalWidth > 0) {
        let edgeInset = (collectionWidth - totalWidth) / 2
        return UIEdgeInsets.init(top: flowLayout.sectionInset.top, left: edgeInset, bottom: flowLayout.sectionInset.bottom, right: edgeInset)
    } else {
        return flowLayout.sectionInset
    }
}

Please make sure your class conforms to UICollectionViewDelegateFlowLayout protocol.

Zink answered 6/11, 2019 at 9:43 Comment(0)
M
5

Swift 4.2 (Horizontally and Vertically). It's small upgrade of Pink Panther code and big thanks him!


func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
    let flowLayout = collectionViewLayout as! UICollectionViewFlowLayout
    let cellWidth: CGFloat = flowLayout.itemSize.width
    let cellHieght: CGFloat = flowLayout.itemSize.height
    let cellSpacing: CGFloat = flowLayout.minimumInteritemSpacing
    let cellCount = CGFloat(collectionView.numberOfItems(inSection: section))
    var collectionWidth = collectionView.frame.size.width
    var collectionHeight = collectionView.frame.size.height
    if #available(iOS 11.0, *) {
        collectionWidth -= collectionView.safeAreaInsets.left + collectionView.safeAreaInsets.right
        collectionHeight -= collectionView.safeAreaInsets.top + collectionView.safeAreaInsets.bottom
    }
    let totalWidth = cellWidth * cellCount + cellSpacing * (cellCount - 1)
    let totalHieght = cellHieght * cellCount + cellSpacing * (cellCount - 1)
    if totalWidth <= collectionWidth {
        let edgeInsetWidth = (collectionWidth - totalWidth) / 2

        print(edgeInsetWidth, edgeInsetWidth)
        return UIEdgeInsets(top: 5, left: edgeInsetWidth, bottom: flowLayout.sectionInset.top, right: edgeInsetWidth)
    } else {
        let edgeInsetHieght = (collectionHeight - totalHieght) / 2
        print(edgeInsetHieght, edgeInsetHieght)
        return UIEdgeInsets(top: edgeInsetHieght, left: flowLayout.sectionInset.top, bottom: edgeInsetHieght, right: flowLayout.sectionInset.top)

    }
}

Make sure your class conforms to UICollectionViewDelegateFlowLayout protocol

Magbie answered 9/12, 2018 at 14:16 Comment(0)
T
4

Swift 5 modification of the currently accepted answer that fixes the issues when your cells can't fit the screen width.

Make sure to replace cellWidth and cellSpacing with your own custom values.

func collectionView(_ collectionView: UICollectionView,
                        layout collectionViewLayout: UICollectionViewLayout,
                        insetForSectionAt section: Int) -> UIEdgeInsets {
    let cellCount = collectionView.numberOfItems(inSection: section)
    let totalCellWidth = cellWidth * CGFloat(cellCount)
    let totalSpacingWidth = cellSpacing * CGFloat(cellCount - 1)
    let collectionViewInset = collectionView.contentInset.left + collectionView.contentInset.right

    let inset = (collectionView.frame.width - collectionViewInset - totalCellWidth - totalSpacingWidth) / 2

    return inset > 0
        ? UIEdgeInsets(top: 0, left: inset, bottom: 0, right: inset)
        : UIEdgeInsets.zero
}
Trainload answered 2/2, 2022 at 14:9 Comment(0)
U
3

Swift 4

extension ViewController: UICollectionViewDelegateFlowLayout {

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {

        let cellWidth: CGFloat = 170.0 // Your cell width

        let numberOfCells = floor(view.frame.size.width / cellWidth)
        let edgeInsets = (view.frame.size.width - (numberOfCells * cellWidth)) / (numberOfCells + 1)

        return UIEdgeInsetsMake(0, edgeInsets, 0, edgeInsets)
    }

 }
Underdone answered 4/5, 2018 at 22:10 Comment(0)
B
3

For the people who only want to add a padding (top, left, bottom, right):

Add the protocol UICollectionViewDelegateFlowLayout

This example shows a padding left and right with 40.

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {        
    return UIEdgeInsetsMake(0, 40, 0, 40)
}
Bluff answered 14/11, 2018 at 23:19 Comment(0)
D
2

SWIFT 4.2

private lazy var contentView: UICollectionView = {
        let layoutView: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
            layoutView.scrollDirection = .horizontal
            layoutView.minimumInteritemSpacing = 0
            layoutView.minimumLineSpacing = 5

        let collectionView: UICollectionView = UICollectionView(frame: .zero, collectionViewLayout: layoutView)
            collectionView.dataSource = self
            collectionView.delegate = self
            collectionView.showsHorizontalScrollIndicator = false
            collectionView.isPagingEnabled = true
            collectionView.registerCell(Cell.self)
            collectionView.backgroundColor = .clear
            collectionView.translatesAutoresizingMaskIntoConstraints = false
        return collectionView
    }()

//

extension CustomCollectionView: UICollectionViewDelegateFlowLayout {
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {

        return CGSize(width: collectionView.frame.width*4/5, height: collectionView.frame.height)
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
        let cellWidth : CGFloat = collectionView.frame.width*4/5

        let numberOfCells = floor(collectionView.frame.width / cellWidth)
        let edgeInsets = (collectionView.frame.width - (numberOfCells * cellWidth)) / (numberOfCells + 1)

        return UIEdgeInsets(top: 0, left: edgeInsets, bottom: 0, right: edgeInsets)
    }
}
Dumm answered 17/11, 2018 at 21:50 Comment(0)
K
2

If you're using Darshan Patel's answer, you can also perform the calculation inside UICollectionViewFlowLayout subclass.

class Layout: UICollectionViewFlowLayout {

    override init() {
        super.init()
        scrollDirection = .horizontal
    }

    @available(*, unavailable)
    required init?(coder: NSCoder) {
        fatalError()
    }

    override func prepare() {
        super.prepare()

        guard let collectionView = collectionView,
            collectionView.numberOfSections != 0 else { return }
    
        minimumInteritemSpacing = 5
        itemSize = CGSize(width: 30, height: 30)

        sectionInsetReference = .fromSafeArea

        let numberOfItems = collectionView.numberOfItems(inSection: 0)

        let totalCellWidth = itemSize.width * CGFloat(numberOfItems)
        let totalSpacingWidth = minimumInteritemSpacing * CGFloat(numberOfItems - 1)

        let leftInset = (collectionView.bounds.maxX - CGFloat(totalCellWidth + totalSpacingWidth)) / 2

        sectionInset = UIEdgeInsets(top: 0, left: leftInset, bottom: 0, right: leftInset)
    }
}
Kolodgie answered 23/3, 2021 at 15:17 Comment(0)
M
1

You can try my solution it works fine,

func refreshCollectionView(_ count: Int) {
    let collectionViewHeight = collectionView.bounds.height
    let collectionViewWidth = collectionView.bounds.width
    let numberOfItemsThatCanInCollectionView = Int(collectionViewWidth / collectionViewHeight)
    if numberOfItemsThatCanInCollectionView > count {
        let totalCellWidth = collectionViewHeight * CGFloat(count)
        let totalSpacingWidth: CGFloat = CGFloat(count) * (CGFloat(count) - 1)
        // leftInset, rightInset are the global variables which I am passing to the below function
        leftInset = (collectionViewWidth - CGFloat(totalCellWidth + totalSpacingWidth)) / 2;
        rightInset = -leftInset
    } else {
        leftInset = 0.0
        rightInset = -collectionViewHeight
    }
    collectionView.reloadData()
}

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
    return UIEdgeInsetsMake(0, leftInset, 0, rightInset)
}
Mcwherter answered 16/3, 2017 at 6:23 Comment(0)
O
1

The accepted answer is the right answer but if your totalCellWidth is less than the CollectionView's width, but just to guard against this you can do as below.

if (leftInset > 0) {
     return UIEdgeInsetsMake(0, leftInset, 0, rightInset)
  } else {
     return UIEdgeInsetsMake(0, 10, 0, 10)
}
Onward answered 23/9, 2018 at 3:44 Comment(0)
Z
1

This code should center horizontally collection view even in Swift 4.0 without any modification:

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
    let flowLayout = collectionViewLayout as! UICollectionViewFlowLayout
    let cellWidth: CGFloat = flowLayout.itemSize.width
    let cellSpacing: CGFloat = flowLayout.minimumInteritemSpacing
    let cellCount = CGFloat(collectionView.numberOfItems(inSection: section))
    var collectionWidth = collectionView.frame.size.width
    if #available(iOS 11.0, *) {
        collectionWidth -= collectionView.safeAreaInsets.left + collectionView.safeAreaInsets.right
    }
    let totalWidth = cellWidth * cellCount + cellSpacing * (cellCount - 1)
    if totalWidth <= collectionWidth {
        let edgeInset = (collectionWidth - totalWidth) / 2
        return UIEdgeInsetsMake(flowLayout.sectionInset.top, edgeInset, flowLayout.sectionInset.bottom, edgeInset)
    } else {
        return flowLayout.sectionInset
    }
}

Make sure your class conforms to UICollectionViewDelegateFlowLayout protocol

Zink answered 3/12, 2018 at 6:13 Comment(0)
E
1

If there is only room for one cell per group, a leading: and trailing: of .flexible(0) will center the cell horizontally:

item.edgeSpacing = NSCollectionLayoutEdgeSpacing(
    leading: .flexible(0), top: nil,                                                     
    trailing: .flexible(0), bottom: nil
)
Eachern answered 17/1, 2020 at 22:50 Comment(0)
F
1

I've dealed with the same problem and as a solution I've copied layout from this project and applied it to my UICollectionView as a UICollectionViewFlowLayout. You don't need apply the full library, just copy layout class.

https://github.com/Coeur/CollectionViewCenteredFlowLayout

Firstly answered 18/3, 2022 at 10:49 Comment(0)
K
1

Change cellWidth and paddingInside

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
        let cellWidth : CGFloat = 100
        let paddingInside: CGFloat = 10
        
        let numberOfCells = floor(collectionView.frame.size.width / cellWidth)
        let edgeInsets = (collectionView.frame.size.width - (numberOfCells * cellWidth)) / 2 - paddingInside
        
        
        return UIEdgeInsets(top: 0, left: edgeInsets, bottom: 0, right: edgeInsets)
    }

and do no forget confirm your vc to UICollectionViewDelegateFlowLayout protocol

Katricekatrina answered 29/11, 2022 at 11:29 Comment(0)
R
0

I ended up taking a completely different approach here, which I believe is worth mentioning.

I set a constraint on my collection view to be horizontally aligned in the center. Then I set another constraint that specifies the width. I created an outlet for the width constraint inside of my viewController that holds the collection view. Then, when my data source is changed and I am updating the collection view, I take the count of the cells and do a (very similar) calculation to reset the width.

let newWidth = (items.count * cellWidth) + (items.count * cellSpacing)

Then I set the constraint outlet's .constant value to the calculation result and autolayout does the rest.

This may conflict with the `UICollectionViewDelegateFlowLayout, but this worked perfectly for me to create a left-justified collection view. Without a delegate, it only seems to work when the cells filled up the majority of the view.

Roid answered 19/2, 2019 at 21:4 Comment(0)
G
0

I have similar situation in project, and I fixed it by referring UPCarouselFlowLayout

I think it support swift 5 version

https://github.com/ink-spot/UPCarouselFlowLayout/blob/master/UPCarouselFlowLayout/UPCarouselFlowLayout.swift

Look at the code implementation in

override open func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint
Grig answered 5/12, 2019 at 6:5 Comment(0)
A
0

the simplest way is to set collection view estimate size to None in storyboard or with code layout.estimatedItemSize = CGSize.zero

Aposematic answered 29/12, 2019 at 14:13 Comment(0)
S
0

I used that code in a project. It centers the collectionView horizontally and vertically in both direction .horizontal and .vertical using the insets of section. Respects the spacing and the original inset of the section if set. Code to use in the delegate UICollectionViewDelegateFlowLayout so we have access to all the properties we need to retrieve from the UIcollectionView or set in the storyboard for reusability.

// original function of the delegate
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
    // casting the layout as a UICollectionViewFlowLayout to have access to the properties of items for reusability - you could also link the real one from the storyboard with an outlet
    let flowLayout = collectionViewLayout as! UICollectionViewFlowLayout
    // getting all the properties we need
    let itemWidth = flowLayout.itemSize.width
    let itemHeight = flowLayout.itemSize.height
    let interSpacing = flowLayout.minimumInteritemSpacing
    let lineSpacing = flowLayout.minimumLineSpacing
    // getting the size of the collectionView
    let collectionWidth = collectionView.bounds.width
    let collectionHeight = collectionView.bounds.height
    // getting the direction to choose how to align the collection
    let direction = flowLayout.scrollDirection
    // you don't want to have an item greater than the collection
    guard (itemWidth < collectionWidth && direction == .vertical) || (itemHeight < collectionHeight && direction == .horizontal) else {
        print("Really?")
        return UIEdgeInsets(top: flowLayout.sectionInset.top, left: flowLayout.sectionInset.left, bottom: flowLayout.sectionInset.bottom, right: flowLayout.sectionInset.right)
    }
    // getting the number of item in the current section
    let totalItemCount = CGFloat(collectionView.numberOfItems(inSection: section))
    // setting number of item in a row to the max number of items that can fit in a row without spacing or to the number of items in the section if less than the max
    var itemCountInRow = totalItemCount < (collectionWidth / itemWidth).rounded(.towardZero) ? totalItemCount : (collectionWidth / itemWidth).rounded(.towardZero)
    // how many max row can we have
    var countOfRow = totalItemCount < (collectionHeight / itemHeight).rounded(.towardZero) ? totalItemCount : (collectionHeight / itemHeight).rounded(.towardZero)
    // calculating the total width of row by multiplying the number of items in the row by the width of item and adding the spacing multiplied by the number of item minus one
    var totalWidthOfRow:CGFloat {
        get{
            return (itemWidth * itemCountInRow) + (interSpacing * (itemCountInRow - 1))
        }
    }
    // calculating the total height of row by multiplying the number of row by the height of item and adding the spacing multiplied by the number of row minus one
    var totalHeightOfRow:CGFloat {
        get{
            return (itemHeight * countOfRow) + (lineSpacing * (countOfRow - 1))
        }
    }
    // first we set the inset to the default
    var edgeInsetLeft = flowLayout.sectionInset.left
    var edgeInsetTop = flowLayout.sectionInset.top

    if direction == .vertical {
        // while the width of row with original margin is greater than the width of the collection we drop one item until it fits
        while totalWidthOfRow > collectionWidth || ((collectionWidth - totalWidthOfRow) / 2) < flowLayout.sectionInset.left {
            // droping an item to fit in the row
            itemCountInRow -= 1
        }
        // calculating the number of rows in collectionView by dividing the number of items by the number of items in a row
        countOfRow = (totalItemCount / (itemCountInRow)).rounded(.up)
    } else {
        itemCountInRow = (totalItemCount / countOfRow).rounded(.up)
        // while the height of row with original marginis greater than the height of the collection we drop one row until it fits
        while totalHeightOfRow >= collectionHeight  || ((collectionHeight - totalHeightOfRow) / 2) < flowLayout.sectionInset.top  {
            // droping an item to fit in the row
            countOfRow -= 1
        }
    }
    edgeInsetLeft = max(flowLayout.sectionInset.left, (collectionWidth - totalWidthOfRow) / 2)
    edgeInsetTop = max(flowLayout.sectionInset.top, (collectionHeight - totalHeightOfRow) / 2)
    // we don't specially need insets where the items are overflowing
    let edgeInsetRight = direction == .vertical ? edgeInsetLeft : flowLayout.sectionInset.right
    let edgeInsetBottom = direction == .horizontal ? edgeInsetTop : flowLayout.sectionInset.bottom
    // returning the UIEdgeInsets
    return UIEdgeInsets(top: edgeInsetTop, left: edgeInsetLeft, bottom: edgeInsetBottom, right: edgeInsetRight)
}

Hope it would help someone - it centers the section not the items inside the section, for more we have to subclass the UICollectionViewFlowLayout or UICollectionViewLayout as the mosaic example from Apple.

Strobotron answered 9/2, 2020 at 23:11 Comment(0)
D
0

There is one improvement in the above answer for when the data is more; Use following code:

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
      var totalCellsWidth:CGFloat = 0
        for index in 0..<numberOfCells{
            let width = viewModel.getCellWidth(at: index) // get cell width is to dynamically calculate the width of the cell according to the text
            totalCellsWidth += width
        }
        let cellSpacing:CGFloat = 8.0
        let totalSpacingWidth = cellSpacing * CGFloat(viewModel.teamsCount - 1)
       
        var leftInset = (collectionView.frame.width - (totalCellsWidth + totalSpacingWidth)) / 2
        if leftInset < 0{
            leftInset = 0
        }
        let rightInset = leftInset
        return UIEdgeInsets(top: 0, left: leftInset, bottom: 0, right: rightInset)
}
Doordie answered 10/11, 2020 at 7:55 Comment(0)
M
0

You have to define a custom UICollectionViewFlowLayout here as bellow.

class CenterAlignedCollectionViewFlowLayout: UICollectionViewFlowLayout {
    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        guard let superArray = super.layoutAttributesForElements(in: rect) else {  return nil  }
        guard let attributes = NSArray(array: superArray, copyItems: true) as? [UICollectionViewLayoutAttributes] else {  return nil  }
        // Constants
        let leftPadding: CGFloat = 8
        let interItemSpacing = minimumInteritemSpacing
        // Tracking values
        var leftMargin: CGFloat = leftPadding // Modified to determine origin.x for each item
        var maxY: CGFloat = -1.0 // Modified to determine origin.y for each item
        var rowSizes: [[CGFloat]] = [] // Tracks the starting and ending x-values for the first and last item in the row
        var currentRow: Int = 0 // Tracks the current row
        attributes.forEach { layoutAttribute in
            // Each layoutAttribute represents its own item
            if layoutAttribute.frame.origin.y >= maxY {
                // This layoutAttribute represents the left-most item in the row
                leftMargin = leftPadding
                // Register its origin.x in rowSizes for use later
                if rowSizes.count == 0 {
                    // Add to first row
                    rowSizes = [[leftMargin, 0]]
                } else {
                    // Append a new row
                    rowSizes.append([leftMargin, 0])
                    currentRow += 1
                }
            }
            layoutAttribute.frame.origin.x = leftMargin
            leftMargin += layoutAttribute.frame.width + interItemSpacing
            maxY = max(layoutAttribute.frame.maxY, maxY)
            // Add right-most x value for last item in the row
            rowSizes[currentRow][1] = leftMargin - interItemSpacing
        }
        // At this point, all cells are left aligned
        // Reset tracking values and add extra left padding to center align entire row
        leftMargin = leftPadding
        maxY = -1.0
        currentRow = 0
        attributes.forEach { layoutAttribute in
            // Each layoutAttribute is its own item
            if layoutAttribute.frame.origin.y >= maxY {
                // This layoutAttribute represents the left-most item in the row
                leftMargin = leftPadding
                // Need to bump it up by an appended margin
                let rowWidth = rowSizes[currentRow][1] - rowSizes[currentRow][0] // last.x - first.x
                let appendedMargin = (collectionView!.frame.width - leftPadding  - rowWidth - leftPadding) / 2
                leftMargin += appendedMargin
                currentRow += 1
            }
            layoutAttribute.frame.origin.x = leftMargin
            leftMargin += layoutAttribute.frame.width + interItemSpacing
            maxY = max(layoutAttribute.frame.maxY, maxY)
        }
        return attributes
    }
}

Then add following to the relevant viewController's viewDidLoad or relevant method,

let layout = CenterAlignedCollectionViewFlowLayout()
layout.estimatedItemSize = CGSize(width: 200, height: 40)
self.collectionRateCustomer.collectionViewLayout = layout

Don't forget to inherit UICollectionViewDelegateFlowLayout

References, equaleyes.com and Alex Koshy's answer

Metaphrast answered 10/8, 2021 at 22:57 Comment(0)
E
0

another approach is to divide the space you have by the number of cells you want to display, this way you will mimic the .FillEqually mode of stackViews

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    return CGSize(width: collectionView.frame.width / CGFloat(collectionView.numberOfItems(inSection: indexPath.section)), height: 28)
}

don't forget to :

  • set the view in the cell contentview to a fixed width, centered in it's container without trailing and leading constraint
  • remove any minimum spacing for cells and lines in the inspector of the CollectionView, set them to 0 each
  • provide a height in the snippet of code above, instead of 28 as it is right now
Etherify answered 7/10, 2021 at 13:44 Comment(0)
F
0

In multiple rows, this worked for me:

    func collectionView(_ collectionView: UICollectionView,
                    layout collectionViewLayout: UICollectionViewLayout,
                    insetForSectionAt section: Int) -> UIEdgeInsets {

    let cellWidth = 370
    let cellSpaceing = 10
    let totalCellWithSpace = cellWidth + cellSpaceing
    let howManyCellsInRow = Int(Int(collectionView.layer.frame.size.width) / (totalCellWithSpace))
    let inset = (collectionView.layer.frame.size.width - CGFloat(howManyCellsInRow * totalCellWithSpace)) / 2
    return UIEdgeInsets(top: 0, left: inset, bottom: 0, right: inset)

}
Fluorene answered 16/11, 2021 at 9:30 Comment(0)
A
0

Set insets by collectionView content size. Below i have used cell width by its text length. collectionView center and cell size according to text

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
            let font = FontNames.mediumFont12
            let fontAttributes = [NSAttributedString.Key.font: font]
            let text = "Your text"
            let size = (text as NSString).size(withAttributes: fontAttributes)
            self.setInsets(width: self.tagsCollectionView.contentSize.width)
            return CGSize(width: size.width+30.0, height: self.tagsCollectionView.bounds.height)
}
func setInsets(width: CGFloat) {
            let inset = self.tagsCollectionView.bounds.width - width
            if inset <= 26.0 {
                self.tagsCollectionView.contentInset = UIEdgeInsets(top: 0.0, left: 26.0, bottom: 0.0, right: 26.0)
            } else {
                self.tagsCollectionView.contentInset = UIEdgeInsets(top: 0.0, left: inset/2.0, bottom: 0.0, right: inset/2.0)
            }
        }
Assent answered 9/11, 2022 at 6:37 Comment(0)
C
0

Assuming the collectionView is constrained to the bounds of a view and that the collectionView's content can be contained within the width of the view...

    NSLayoutConstraint.activate([
       collectionView.topAnchor.constraint(equalTo: view.topAnchor),
       collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
       collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
       collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
    ]

You could try something like this where you reload your collectionView:

    collectionView?.reload()
    let contentSize = collectionView.collectionViewLayout.collectionViewContentSize
    let space = view.bounds.size.width - contentSize.width
    collectionView.contentInset = UIEdgeInsets(top: 0, left: space / 2, bottom: 0, right: space / 2)

Alternatively, you could main a reference (variable) to the collectionView.leadingAnchor.constraint, and just change that constraint's offset to space / 2 after reloading the table and that would keep the collectionView content from scrolling horizontally, for example if you'd set the layout scrollDirection to .horizontal

Clein answered 26/4, 2023 at 23:40 Comment(0)
O
-1

For swift 5.2

I made some adjustments to meet my requirements

  1. Not to scroll if the content didn't extend outside the screen
  2. Center if the content didn't extend outside the screen
  3. If the content did extend outside the screen make sure to start at index 0 and allow scrolling while indenting from the edge by 16 (easy to adjust)
func centerItemsInCollectionView(cellWidth: Double, numberOfItems: Double, spaceBetweenCell: Double, collectionView: UICollectionView) -> UIEdgeInsets {
        let totalWidth = cellWidth * numberOfItems
        let totalSpacingWidth = spaceBetweenCell * (numberOfItems - 1)
        let leftInset = (collectionView.frame.width - CGFloat(totalWidth + totalSpacingWidth)) / 2
        let rightInset = leftInset
        if leftInset < 0 {
            collectionView.isScrollEnabled = true
            return UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16)
        }
        collectionView.isScrollEnabled = false
        return UIEdgeInsets(top: 0, left: leftInset, bottom: 0, right: rightInset)
    }

Use this by doing this in your view controller

extension BaseViewController : UICollectionViewDelegateFlowLayout {

then you can call

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
        return centerItemsInCollectionView(cellWidth: 70, numberOfItems: 3, spaceBetweenCell: 25, collectionView: collectionView)
}

keep in mind if you want to set your own size for your cells you need to set the collectionView Estimated Size to none in storyboard and set your cells size to custom in storyboard

then you can call

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        return CGSize.init(width:60, height: 60)
}

if you are struggling to get methods to get called it's good to get all your delegates listened for

extension BaseViewController : UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {

and obviously don't forget to set your delegates, simply extending them won't do the trick.

override func viewDidLoad() {
        super.viewDidLoad()
        collectionView.delegate = self
        collectionView.dataSource = self
...
Okajima answered 29/1, 2021 at 16:38 Comment(0)
P
-5

I think you need to centre cell , so instead of using collectionView I'ld like UITableView will be of great use. Just use a UIViewController and place two UIViews in front and back and place a UITableView in middle Hope this helps

Photoelectron answered 14/12, 2015 at 15:23 Comment(1)
I added a picture, to show you what exactly I need, thanks!Neurocoele

© 2022 - 2024 — McMap. All rights reserved.